using System;
using System.Collections.Generic;
using System.Threading;
using System.Collections;

namespace IBNet.UtilsDataStructuresAndExtensions
{
   /// <summary>
   /// A thread safe dictionary.
   /// </summary>
   /// <typeparam name="TKey">The type of keys in the dictionary.</typeparam>
   /// <typeparam name="TValue">The type of values in the dictionary.</typeparam>
   public class ThreadSafeDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IDisposable
   {
      private readonly Dictionary<TKey, TValue> dictionary;
      private readonly ReaderWriterLockSlim dictionaryLock = new ReaderWriterLockSlim();
      private bool isDisposed;

      /// <summary>
      /// Creates a new ThreadSafeDictionary&lt;TKey, TValue>.
      /// </summary>
      public ThreadSafeDictionary()
      {
         this.dictionary = new Dictionary<TKey, TValue>();
      }

      /// <summary>
      /// Creates a new ThreadSafeDictionary&lt;TKey, TValue> with a specified capacity.
      /// </summary>
      /// <param name="capacity">The initial number of elements the dictionary can contain.</param>
      public ThreadSafeDictionary(int capacity)
      {
         this.dictionary = new Dictionary<TKey, TValue>(capacity);
      }

      /// <summary>
      /// Adds the specified key and value to the dictionary.
      /// </summary>
      /// <param name="key">The key of the element to add.</param>
      /// <param name="value">The value of the element to add. The value can be null for reference types.</param>
      public void Add(TKey key, TValue value)
      {
         dictionaryLock.EnterWriteLock();
         try
         {
            dictionary.Add(key, value);
         }
         finally
         {
            dictionaryLock.ExitWriteLock();
         }
      }

      /// <summary>
      /// Removes the value with the specified key from the dictionary.
      /// </summary>
      /// <param name="key">The key of the element to remove.</param>
      /// <returns>true if the element is successfully found and removed; otherwise, false. This method returns false if key is not found in the dictionary.</returns>
      public bool Remove(TKey key)
      {
         bool keyWasPresentInDictionary;
         dictionaryLock.EnterUpgradeableReadLock();
         try
         {
            if (dictionary.ContainsKey(key))
            {
               keyWasPresentInDictionary = true;
               dictionaryLock.EnterWriteLock();
               try
               {
                  dictionary.Remove(key);
               }
               finally
               {
                  dictionaryLock.ExitWriteLock();
               }
            }
            else
            {
               keyWasPresentInDictionary = false;
            }
         }
         finally
         {
            dictionaryLock.ExitUpgradeableReadLock();
         }
         return keyWasPresentInDictionary;
      }

      /// <summary>
      /// Upserts the specified key and value to the dictionary.
      /// </summary>
      /// <param name="key">The key to insert or update.</param>
      /// <param name="value">The value to insert or update.</param>
      /// <returns>UpsertType.Insert if the key is not present in the dictionary and the key is inserted sucessfully; otherwise, UpsertType.Update if the key is present in the dictionary and value associated with the key was updated successfully.</returns>
      public UpsertResult Upsert(TKey key, TValue value)
      {
         UpsertResult upsertResult = UpsertResult.Update;
         TValue currentValue;
         dictionaryLock.EnterUpgradeableReadLock();
         try
         {
            if (dictionary.TryGetValue(key, out currentValue))
            {
               if (!value.Equals(currentValue))
               {
                  dictionaryLock.EnterWriteLock();
                  try
                  {
                     dictionary[key] = value;
                  }
                  finally
                  {
                     dictionaryLock.ExitWriteLock();
                  }
               }
            }
            else
            {
               upsertResult = UpsertResult.Insert;
               dictionaryLock.EnterWriteLock();
               try
               {
                  dictionary.Add(key, value);
               }
               finally
               {
                  dictionaryLock.ExitWriteLock();
               }
            }
         }
         finally
         {
            dictionaryLock.ExitUpgradeableReadLock();
         }
         return upsertResult;
      }

      /// <summary>
      /// Determines whether the dictionary contains the specified key.
      /// </summary>
      /// <param name="key">The key to locate in the dictionary.</param>
      /// <returns>true if the dictionary contains an element with the specified key; otherwise, false.</returns>
      public bool ContainsKey(TKey key)
      {
         bool containsKey;
         dictionaryLock.EnterReadLock();
         try
         {
            containsKey = dictionary.ContainsKey(key);
         }
         finally
         {
            dictionaryLock.ExitReadLock();
         }
         return containsKey;
      }

      /// <summary>
      /// Determines whether the dictionary contains a specific value.
      /// </summary>
      /// <param name="value">The value to locate in the dictionary. The value can be null for reference types.</param>
      /// <returns>true if the dictionary contains an element with the specified value; otherwise, false.</returns>
      public bool ContainsValue(TValue value)
      {
         bool doesContainValue;
         dictionaryLock.EnterReadLock();
         try
         {
            doesContainValue = dictionary.ContainsValue(value);
         }
         finally
         {
            dictionaryLock.ExitReadLock();
         }
         return doesContainValue;
      }

      /// <summary>
      /// Gets the value associated with the specified key.
      /// </summary>
      /// <param name="key">The key of the value to get.</param>
      /// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.</param>
      /// <returns>true if the ThreadSafeDictionary&lt;T> contains an element with the specified key; otherwise, false.</returns>
      public bool TryGetValue(TKey key, out TValue value)
      {
         bool doesContainsKey;
         dictionaryLock.EnterReadLock();
         try
         {
            doesContainsKey = dictionary.TryGetValue(key, out value);
         }
         finally
         {
            dictionaryLock.ExitReadLock();
         }
         return doesContainsKey;
      }

      /// <summary>
      /// Gets or sets the value associated with key.
      /// </summary>
      /// <param name="key">The key of the value to get or set.</param>
      /// <returns>The value associated with key.</returns>
      public TValue this[TKey key]
      {
         get
         {
            TValue value;
            dictionaryLock.EnterReadLock();
            try
            {
               value = dictionary[key];
            }
            finally
            {
               dictionaryLock.ExitReadLock();
            }
            return value;
         }
         set
         {
            dictionaryLock.EnterWriteLock();
            try
            {
               dictionary[key] = value;
            }
            finally
            {
               dictionaryLock.ExitWriteLock();
            }
         }
      }

      /// <summary>
      /// Gets a collection containing the keys in the ThreadSafeDictionary&lt;T>.
      /// </summary>
      public ICollection<TKey> Keys
      {
         get
         {
            ICollection<TKey> keys;
            dictionaryLock.EnterReadLock();
            try
            {
               keys = dictionary.Keys;
            }
            finally
            {
               dictionaryLock.ExitReadLock();
            }
            return keys;
         }
      }

      /// <summary>
      /// Gets a collection containing the values in the ThreadSafeDictionary&lt;T>.
      /// </summary>
      public ICollection<TValue> Values
      {
         get
         {
            ICollection<TValue> values;
            dictionaryLock.EnterReadLock();
            try
            {
               values = dictionary.Values;
            }
            finally
            {
               dictionaryLock.ExitReadLock();
            }
            return values;
         }
      }

      /// <summary>
      /// Removes all elements from the ThreadSafeDictionary&lt;T>.
      /// </summary>
      public void Clear()
      {
         dictionaryLock.EnterUpgradeableReadLock();
         try
         {
            if (dictionary.Count > 0)
            {
               dictionaryLock.EnterWriteLock();
               try
               {
                  dictionary.Clear();
               }
               finally
               {
                  dictionaryLock.ExitWriteLock();
               }
            }
         }
         finally
         {
            dictionaryLock.ExitUpgradeableReadLock();
         }
      }

      /// <summary>
      /// Gets the number of key/value pairs contained in the ThreadSafeDictionary&lt;TKey, TValue>.
      /// </summary>
      public int Count
      {
         get
         {
            int count;
            dictionaryLock.EnterReadLock();
            try
            {
               count = dictionary.Count;
            }
            finally
            {
               dictionaryLock.ExitReadLock();
            }
            return count;
         }
      }

      /// <summary>
      /// Returns an enumerator that iterates through the ThreadSafeDictionary&lt;T>.
      /// </summary>
      /// <returns></returns>
      public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
      {
         return dictionary.GetEnumerator();
      }

      /// <summary>
      /// Returns an enumerator that iterates through the ThreadSafeDictionary&lt;T>.
      /// </summary>
      /// <returns></returns>
      IEnumerator IEnumerable.GetEnumerator()
      {
         return dictionary.GetEnumerator();
      }

      /// <summary>
      /// Disposes of the internal ReaderWriterLockSlim instance.
      /// </summary>
      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }

      protected virtual void Dispose(bool disposing)
      {
         if (isDisposed) return;

         if (disposing)
         {
            if (dictionaryLock != null)
            {
               dictionaryLock.Dispose();
               isDisposed = true;
            }
         }
      }
   }
}